Shiny
Shiny is an R package that makes it easy to build interactive web apps straight from R. You can host standalone apps on a webpage or embed them in R Markdown documents or build dashboards. You can also extend your Shiny apps with CSS themes, htmlwidgets, and JavaScript actions.
Shiny is most often used with the packages “shinydashboard” or “flexdashboard” to build interactive dashboards. We will walk through how to use both shinydashboard and flexdashboard to give you options when using shiny.
Shiny Outline
If you were to open a Shiny app while creating a new file in R, an outline of the an app will be shown using the Old Faithful Geyser Data. This example app shows eruption wait times in a histogram with a side panel to change the number of bins in the histogram. A simple example of what these apps are intended for, user interaction.
## Old Faithful Geyser Example
library(shiny)
# Define UI for application that draws a histogram
ui <- fluidPage(
# Application title
titlePanel("Old Faithful Geyser Data"),
# Sidebar with a slider input for number of bins
sidebarLayout(
sidebarPanel(
sliderInput("bins",
"Number of bins:",
min = 1,
max = 50,
value = 30)
),
# Show a plot of the generated distribution
mainPanel(
plotOutput("distPlot")
)
)
)
# Define server logic required to draw a histogram
server <- function(input, output) {
output$distPlot <- renderPlot({
# generate bins based on input$bins from ui.R
x <- faithful[, 2]
bins <- seq(min(x), max(x), length.out = input$bins + 1)
# draw the histogram with the specified number of bins
hist(x, breaks = bins, col = 'darkgray', border = 'white')
})
}
# Run the application
shinyApp(ui = ui, server = server)
Viewing this example can be useful to see the flow of the user interface and processes. So let’s go through what those terms mean. There are essentially three steps to any Shiny App:
- Create the User interface – ui
- Create the processes – server
- Run the app – shinyApp
The two main parts of the Shiny app are the ui and the server. These objects are the arguments used in the function to run the app, shinyApp(ui, server). We will create an ui and server object for these arguments and name them what they are, ui and server. This will help us from mixing up them up and make our lives easier.
Shiny and Shinydashboard
We will mostly be talking about shinydashboard in this walkthrough. There is no reason to worry as the two packages are essentially the same, but shinydashboard puts your code into an already styled theme in the ui without having to write the code yourself.
Shiny dashboard structure (UI - user interface)
The ui object controls the layout and appearance of the app. This includes formatting, color, text, input panels, main panels, etc. (maybe go into detail here). ui writes your code into html for you. Alternatively, you could use the html functions as well.
For shinydashboard we design parts of the ui and then combine it together in a function called dashboardPage(). To keep things simple, we will go through each of the main parts individually. Then show how they can be combined in this dashboardPage() to make the comeplete user interface.
3 Main parts:
- Header - dashboardHeader()
- Sidebar - dashboardSidebar()
- Body - dashboardBody()
Render Functions
Functions that you use in your application’s server side code, assigning them to outputs that appear in your user interface.
- renderPrint()
- renderText()
- renderPlot()
- renderUI() # HTML or a shiny tag object
Example Using Star Wars df
library(shiny)
library(tidyverse)
library(shinydashboard)
header <- dashboardHeader(
title = "Example Dashboard Header",
titleWidth = 300
)
sidebar <- dashboardSidebar(
selectInput(
inputId = "name",
label = "Favorite Character",
choices = c(starwars$name)
)
)
body <- dashboardBody(
textOutput("name")
)
After you code the sidebar and body, you can then create the User Interface, or “ui”. The User Interface is built from a header, sidebar, and body. When you modify any of these options, the easiest way is to save it as an object called “header”, “sidebar”, or “body”. The user interface is most often coded into the dashboardPage() function.
ui <- dashboardPage(header = header,
sidebar = sidebar,
body = body
)
server <- function(input, output) {
output$name <- renderText({
input$name
})
}
shinyApp(ui, server)
Shinydashboard Layout
There are two main types of layouts that shiny uses. The first is a “row” based layout, while the other is a “column” based layout. You can use either one, or even a mix of the two. The way to create the layouts is by using the “fluidRow()” function.
These functions are used inside of the dashboardBody() section. Each time “fluidRow()” is called, a new row is created.
Row layout
body <- dashboardBody(
fluidRow(
# Row 1
box(
width = 12, # 12 width spans the entire width of the screen
title = "Regular Box, Row 1",
"Text inside of box"
)),
fluidRow(
# Row 2
box(
width = 12,
title = "Regular Box, Row 2",
"Text inside of box 2"
)
)
)
ui <- dashboardPage(header = dashboardHeader(),
sidebar = dashboardSidebar(),
body = body
)
ui
Column Layout
The next layout option is to build columns. The difference in using columns instead of rows is to use the “column()” function inside of “fluidRow()”.
You can set the width of the column, but when you insert a box or chart, the width needs to be “NULL”.
body <- dashboardBody(
fluidRow(
# Column 1
column(width = 6,
infoBox(
width = NULL, # The width must be "NULL" when using a column layout
title = "Regular Box, Column 1",
"Text inside of box"
)
),
column(width = 6,
# Column 2
infoBox(
width = NULL, # The width must be "NULL" when using a column layout
title = "Regular Box, Column 2",
"Text inside of box"
)
)
)
)
ui <- dashboardPage(header = dashboardHeader(),
sidebar = dashboardSidebar(),
body = body
)
shinyApp(ui, server)
Mix of Column and Row Layout
Note: To create a new row, call another fluidRow() function.
body <- dashboardBody(
fluidRow(
# Row 1
box(
width = 12, # The width must be "NULL" when using a column layout
title = "Regular Box, Row 1",
"Text inside of box"
),
),
fluidRow(
column(width = 6,
# Column 2
infoBox(
width = NULL, # The width must be "NULL" when using a column layout
title = "Regular Box, Row 2, Column 1",
subtitle = "Text inside of box"
)
),
column(width = 6,
infoBox(
width = NULL,
title = "Regular Box, Row 2, Column 2",
subtitle = "Text inside of box"
)
)
)
)
ui <- dashboardPage(header = dashboardHeader(),
sidebar = dashboardSidebar(),
body = body
)
shinyApp(ui, server)
LS0tDQp0aXRsZTogIlNoaW55IFdhbGt0aHJvdWdoIg0Kb3V0cHV0OiBodG1sX25vdGVib29rDQotLS0NCg0KIyBTaGlueQ0KDQpTaGlueSBpcyBhbiBSIHBhY2thZ2UgdGhhdCBtYWtlcyBpdCBlYXN5IHRvIGJ1aWxkIGludGVyYWN0aXZlIHdlYiBhcHBzIHN0cmFpZ2h0IGZyb20gUi4gWW91IGNhbiBob3N0IHN0YW5kYWxvbmUgYXBwcyBvbiBhIHdlYnBhZ2Ugb3IgZW1iZWQgdGhlbSBpbiBSIE1hcmtkb3duIGRvY3VtZW50cyBvciBidWlsZCBkYXNoYm9hcmRzLiBZb3UgY2FuIGFsc28gZXh0ZW5kIHlvdXIgU2hpbnkgYXBwcyB3aXRoIENTUyB0aGVtZXMsIGh0bWx3aWRnZXRzLCBhbmQgSmF2YVNjcmlwdCBhY3Rpb25zLg0KDQpTaGlueSBpcyBtb3N0IG9mdGVuIHVzZWQgd2l0aCB0aGUgcGFja2FnZXMgInNoaW55ZGFzaGJvYXJkIiBvciAiZmxleGRhc2hib2FyZCIgdG8gYnVpbGQgaW50ZXJhY3RpdmUgZGFzaGJvYXJkcy4gV2Ugd2lsbCB3YWxrIHRocm91Z2ggaG93IHRvIHVzZSBib3RoIHNoaW55ZGFzaGJvYXJkIGFuZCBmbGV4ZGFzaGJvYXJkIHRvIGdpdmUgeW91IG9wdGlvbnMgd2hlbiB1c2luZyBzaGlueS4NCg0KIyMgU2hpbnkgT3V0bGluZQ0KDQpJZiB5b3Ugd2VyZSB0byBvcGVuIGEgU2hpbnkgYXBwIHdoaWxlIGNyZWF0aW5nIGEgbmV3IGZpbGUgaW4gUiwgYW4gb3V0bGluZSBvZiB0aGUgYW4gYXBwIHdpbGwgYmUgc2hvd24gdXNpbmcgdGhlIE9sZCBGYWl0aGZ1bCBHZXlzZXIgRGF0YS4gVGhpcyBleGFtcGxlIGFwcCBzaG93cyBlcnVwdGlvbiB3YWl0IHRpbWVzIGluIGEgaGlzdG9ncmFtIHdpdGggYSBzaWRlIHBhbmVsIHRvIGNoYW5nZSB0aGUgbnVtYmVyIG9mIGJpbnMgaW4gdGhlIGhpc3RvZ3JhbS4gQSBzaW1wbGUgZXhhbXBsZSBvZiB3aGF0IHRoZXNlIGFwcHMgYXJlIGludGVuZGVkIGZvciwgdXNlciBpbnRlcmFjdGlvbi4gDQoNCmBgYHtyfQ0KIyMgT2xkIEZhaXRoZnVsIEdleXNlciBFeGFtcGxlDQoNCmxpYnJhcnkoc2hpbnkpDQoNCiMgRGVmaW5lIFVJIGZvciBhcHBsaWNhdGlvbiB0aGF0IGRyYXdzIGEgaGlzdG9ncmFtDQp1aSA8LSBmbHVpZFBhZ2UoDQoNCiAgICAjIEFwcGxpY2F0aW9uIHRpdGxlDQogICAgdGl0bGVQYW5lbCgiT2xkIEZhaXRoZnVsIEdleXNlciBEYXRhIiksDQoNCiAgICAjIFNpZGViYXIgd2l0aCBhIHNsaWRlciBpbnB1dCBmb3IgbnVtYmVyIG9mIGJpbnMgDQogICAgc2lkZWJhckxheW91dCgNCiAgICAgICAgc2lkZWJhclBhbmVsKA0KICAgICAgICAgICAgc2xpZGVySW5wdXQoImJpbnMiLA0KICAgICAgICAgICAgICAgICAgICAgICAgIk51bWJlciBvZiBiaW5zOiIsDQogICAgICAgICAgICAgICAgICAgICAgICBtaW4gPSAxLA0KICAgICAgICAgICAgICAgICAgICAgICAgbWF4ID0gNTAsDQogICAgICAgICAgICAgICAgICAgICAgICB2YWx1ZSA9IDMwKQ0KICAgICAgICApLA0KDQogICAgICAgICMgU2hvdyBhIHBsb3Qgb2YgdGhlIGdlbmVyYXRlZCBkaXN0cmlidXRpb24NCiAgICAgICAgbWFpblBhbmVsKA0KICAgICAgICAgICBwbG90T3V0cHV0KCJkaXN0UGxvdCIpDQogICAgICAgICkNCiAgICApDQopDQoNCiMgRGVmaW5lIHNlcnZlciBsb2dpYyByZXF1aXJlZCB0byBkcmF3IGEgaGlzdG9ncmFtDQpzZXJ2ZXIgPC0gZnVuY3Rpb24oaW5wdXQsIG91dHB1dCkgew0KDQogICAgb3V0cHV0JGRpc3RQbG90IDwtIHJlbmRlclBsb3Qoew0KICAgICAgICAjIGdlbmVyYXRlIGJpbnMgYmFzZWQgb24gaW5wdXQkYmlucyBmcm9tIHVpLlINCiAgICAgICAgeCAgICA8LSBmYWl0aGZ1bFssIDJdDQogICAgICAgIGJpbnMgPC0gc2VxKG1pbih4KSwgbWF4KHgpLCBsZW5ndGgub3V0ID0gaW5wdXQkYmlucyArIDEpDQoNCiAgICAgICAgIyBkcmF3IHRoZSBoaXN0b2dyYW0gd2l0aCB0aGUgc3BlY2lmaWVkIG51bWJlciBvZiBiaW5zDQogICAgICAgIGhpc3QoeCwgYnJlYWtzID0gYmlucywgY29sID0gJ2RhcmtncmF5JywgYm9yZGVyID0gJ3doaXRlJykNCiAgICB9KQ0KfQ0KDQojIFJ1biB0aGUgYXBwbGljYXRpb24gDQpzaGlueUFwcCh1aSA9IHVpLCBzZXJ2ZXIgPSBzZXJ2ZXIpDQoNCmBgYA0KDQpWaWV3aW5nIHRoaXMgZXhhbXBsZSBjYW4gYmUgdXNlZnVsIHRvIHNlZSB0aGUgZmxvdyBvZiB0aGUgdXNlciBpbnRlcmZhY2UgYW5kIHByb2Nlc3Nlcy4gU28gbGV0J3MgZ28gdGhyb3VnaCB3aGF0IHRob3NlIHRlcm1zIG1lYW4uIFRoZXJlIGFyZSBlc3NlbnRpYWxseSB0aHJlZSBzdGVwcyB0byBhbnkgU2hpbnkgQXBwOg0KDQoxLiBDcmVhdGUgdGhlIFVzZXIgaW50ZXJmYWNlIC0tIF9fdWlfXw0KMi4gQ3JlYXRlIHRoZSBwcm9jZXNzZXMgLS0gX19zZXJ2ZXJfXw0KMy4gUnVuIHRoZSBhcHAgLS0gX19zaGlueUFwcF9fDQoNClRoZSB0d28gbWFpbiBwYXJ0cyBvZiB0aGUgU2hpbnkgYXBwIGFyZSB0aGUgX191aV9fIGFuZCB0aGUgX19zZXJ2ZXJfXy4gVGhlc2Ugb2JqZWN0cyBhcmUgdGhlIGFyZ3VtZW50cyB1c2VkIGluIHRoZSBmdW5jdGlvbiB0byBydW4gdGhlIGFwcCwgX19zaGlueUFwcCh1aSwgc2VydmVyKV9fLiBXZSB3aWxsIGNyZWF0ZSBhbiB1aSBhbmQgc2VydmVyIG9iamVjdCBmb3IgdGhlc2UgYXJndW1lbnRzIGFuZCBuYW1lIHRoZW0gd2hhdCB0aGV5IGFyZSwgX3VpXyBhbmQgX3NlcnZlcl8uIFRoaXMgd2lsbCBoZWxwIHVzIGZyb20gbWl4aW5nIHVwIHRoZW0gdXAgYW5kIG1ha2Ugb3VyIGxpdmVzIGVhc2llci4NCg0KIyMgU2hpbnkgYW5kIFNoaW55ZGFzaGJvYXJkDQoNCldlIHdpbGwgbW9zdGx5IGJlIHRhbGtpbmcgYWJvdXQgc2hpbnlkYXNoYm9hcmQgaW4gdGhpcyB3YWxrdGhyb3VnaC4gVGhlcmUgaXMgbm8gcmVhc29uIHRvIHdvcnJ5IGFzIHRoZSB0d28gcGFja2FnZXMgYXJlIGVzc2VudGlhbGx5IHRoZSBzYW1lLCBidXQgc2hpbnlkYXNoYm9hcmQgcHV0cyB5b3VyIGNvZGUgaW50byBhbiBhbHJlYWR5IHN0eWxlZCB0aGVtZSBpbiB0aGUgdWkgd2l0aG91dCBoYXZpbmcgdG8gd3JpdGUgdGhlIGNvZGUgeW91cnNlbGYuIA0KDQoNCiMjIFNoaW55IGRhc2hib2FyZCBzdHJ1Y3R1cmUgKFVJIC0gdXNlciBpbnRlcmZhY2UpDQoNClRoZSBfX3VpX18gb2JqZWN0IGNvbnRyb2xzIHRoZSBsYXlvdXQgYW5kIGFwcGVhcmFuY2Ugb2YgdGhlIGFwcC4gVGhpcyBpbmNsdWRlcyBmb3JtYXR0aW5nLCBjb2xvciwgdGV4dCwgaW5wdXQgcGFuZWxzLCBtYWluIHBhbmVscywgZXRjLiAobWF5YmUgZ28gaW50byBkZXRhaWwgaGVyZSkuIHVpIHdyaXRlcyB5b3VyIGNvZGUgaW50byBodG1sIGZvciB5b3UuIEFsdGVybmF0aXZlbHksIHlvdSBjb3VsZCB1c2UgdGhlIGh0bWwgZnVuY3Rpb25zIGFzIHdlbGwuIA0KDQpGb3Igc2hpbnlkYXNoYm9hcmQgd2UgZGVzaWduIHBhcnRzIG9mIHRoZSB1aSBhbmQgdGhlbiBjb21iaW5lIGl0IHRvZ2V0aGVyIGluIGEgZnVuY3Rpb24gY2FsbGVkIGRhc2hib2FyZFBhZ2UoKS4gVG8ga2VlcCB0aGluZ3Mgc2ltcGxlLCB3ZSB3aWxsIGdvIHRocm91Z2ggZWFjaCBvZiB0aGUgbWFpbiBwYXJ0cyBpbmRpdmlkdWFsbHkuIFRoZW4gc2hvdyBob3cgdGhleSBjYW4gYmUgY29tYmluZWQgaW4gdGhpcyBkYXNoYm9hcmRQYWdlKCkgdG8gbWFrZSB0aGUgY29tZXBsZXRlIHVzZXIgaW50ZXJmYWNlLg0KDQozIE1haW4gcGFydHM6DQoNCiogSGVhZGVyIC0gZGFzaGJvYXJkSGVhZGVyKCkNCiogU2lkZWJhciAtIGRhc2hib2FyZFNpZGViYXIoKQ0KKiBCb2R5IC0gZGFzaGJvYXJkQm9keSgpDQoNCg0KIyMjIEhvdyB0byBidWlsZCBhIFNpZGViYXIgRGFzaGJvYXJkDQoNCmBgYHtyfQ0KbGlicmFyeShzaGlueWRhc2hib2FyZCkNCg0Kc2lkZWJhciA8LSBkYXNoYm9hcmRTaWRlYmFyKA0KICBzaWRlYmFyTWVudSgNCiAgICBtZW51SXRlbSgiRGF0YSIsDQogICAgICB0YWJOYW1lID0gImRhdGEiKSAjVXNlZCBmb3IgbGlua2luZyBpbiB0aGUgYm9keQ0KICApDQopDQpgYGANCg0KSWYgeW91IHdhbnQgbW9yZSBpbnRlcmFjdGl2aXR5IG9uIHlvdXIgZGFzaGJvYXJkLCBzaGlueSBoYXMgc29tZSBmdW5jdGlvbnMgdGhhdCBlbmFibGVzIHlvdSB0byBkbyB0aGF0LiBZb3UgY2FuIHVzZSBhIG51bWJlciBvZiBkaWZmZXJlbnQgaW5wdXQgb3B0aW9ucyB1c2luZyBzaGlueS4gVGhpcyBhbGxvd3MgdGhlIHVzZXIgb2YgdGhlIHdlYnNpdGUgdG8gaW5wdXQgaW5mb3JtYXRpb24gLSB3aGV0aGVyIGl0J3MgdGV4dCwgYSBkYXRlLCBvciB0byBzZWxlY3Qgb3B0aW9ucyBmcm9tIGRhdGEgdGhhdCB5b3UgcHJvdmlkZS4NCg0KRGlmZmVyZW50IGlucHV0IGZ1bmN0aW9uczoNCiogc2VsZWN0SW5wdXQoKSAjIEFsbG93cyB1c2VyIHRvIHNlbGVjdCBhIHNpbmd1bGFyIGl0ZW0gZnJvbSBhIGxpc3QNCiogdGV4dElucHV0KCkgIyBBbGxvd3MgdXNlciB0byBpbnB1dCB0ZXh0DQoqIGRhdGVJbnB1dCgpICMgQWxsb3dzIHVzZXIgdG8gaW5wdXQgYSBkYXRlDQoqIGNoZWNrYm94SW5wdXQoKQ0KDQoNCiMjIyBIb3cgdG8gYnVpbGQgdXNlciBpbnB1dCB3aXRoIHNoaW55ZGFzaGJvYXJkDQoNCmBgYHtyfQ0KbGlicmFyeSgic2hpbnkiKQ0KbGlicmFyeSgidGlkeXZlcnNlIikNCg0Kc2VsZWN0SW5wdXQoDQogIGlucHV0SWQgPSAib2JqZWN0IiwgIyBUaGlzIGlzIHRoZSBuYW1lIHlvdSBkZXNjcmliZWQgdGhlIG9iamVjdCBlbHNld2hlcmUgaW4gdGhlIGFwcGxpY2F0aW9uDQogIGxhYmVsID0gIkZhdm9yaXRlIENoYXJhY3RlciIsICMgVGhlIGxhYmVsIHlvdSB3YW50IHRvIHNob3cgdG8gdGhlIHVzZXINCiAgY2hvaWNlcyA9IGMoc3RhcndhcnMkbmFtZSkgIyBMaXN0IHRoZSBjaG9pY2VzIHRoZSB1c2VyIGhhcyB0byBwaWNrIGZyb20NCiApDQpgYGANCg0KDQojIyMgUmVuZGVyIEZ1bmN0aW9ucyANCkZ1bmN0aW9ucyB0aGF0IHlvdSB1c2UgaW4geW91ciBhcHBsaWNhdGlvbidzIHNlcnZlciBzaWRlIGNvZGUsIGFzc2lnbmluZyB0aGVtIHRvIG91dHB1dHMgdGhhdCBhcHBlYXIgaW4geW91ciB1c2VyIGludGVyZmFjZS4NCg0KKiByZW5kZXJQcmludCgpDQoqIHJlbmRlclRleHQoKQ0KKiByZW5kZXJQbG90KCkNCiogcmVuZGVyVUkoKSAjIEhUTUwgb3IgYSBzaGlueSB0YWcgb2JqZWN0DQoNCiMjIEV4YW1wbGUgVXNpbmcgU3RhciBXYXJzIGRmDQpgYGB7cn0NCmxpYnJhcnkoc2hpbnkpDQpsaWJyYXJ5KHRpZHl2ZXJzZSkNCmxpYnJhcnkoc2hpbnlkYXNoYm9hcmQpDQoNCmhlYWRlciA8LSBkYXNoYm9hcmRIZWFkZXIoDQogIHRpdGxlID0gIkV4YW1wbGUgRGFzaGJvYXJkIEhlYWRlciIsDQogIHRpdGxlV2lkdGggPSAzMDANCikNCg0Kc2lkZWJhciA8LSBkYXNoYm9hcmRTaWRlYmFyKA0KICBzZWxlY3RJbnB1dCgNCiAgICBpbnB1dElkID0gIm5hbWUiLA0KICAgIGxhYmVsID0gIkZhdm9yaXRlIENoYXJhY3RlciIsDQogICAgY2hvaWNlcyA9IGMoc3RhcndhcnMkbmFtZSkNCiAgKQ0KKQ0KDQpib2R5IDwtIGRhc2hib2FyZEJvZHkoDQogIHRleHRPdXRwdXQoIm5hbWUiKQ0KKQ0KYGBgDQoNCg0KQWZ0ZXIgeW91IGNvZGUgdGhlIHNpZGViYXIgYW5kIGJvZHksIHlvdSBjYW4gdGhlbiBjcmVhdGUgdGhlIFVzZXIgSW50ZXJmYWNlLCBvciAidWkiLiBUaGUgVXNlciBJbnRlcmZhY2UgaXMgYnVpbHQgZnJvbSBhIGhlYWRlciwgc2lkZWJhciwgYW5kIGJvZHkuIFdoZW4geW91IG1vZGlmeSBhbnkgb2YgdGhlc2Ugb3B0aW9ucywgdGhlIGVhc2llc3Qgd2F5IGlzIHRvIHNhdmUgaXQgYXMgYW4gb2JqZWN0IGNhbGxlZCAiaGVhZGVyIiwgInNpZGViYXIiLCBvciAiYm9keSIuIFRoZSB1c2VyIGludGVyZmFjZSBpcyBtb3N0IG9mdGVuIGNvZGVkIGludG8gdGhlIGRhc2hib2FyZFBhZ2UoKSBmdW5jdGlvbi4NCg0KDQpgYGB7cn0NCnVpIDwtIGRhc2hib2FyZFBhZ2UoaGVhZGVyID0gaGVhZGVyLA0KICAgICAgICAgICAgICAgICAgICBzaWRlYmFyID0gc2lkZWJhciwNCiAgICAgICAgICAgICAgICAgICAgYm9keSA9IGJvZHkNCiAgICAgICAgICAgICAgICAgICAgKQ0KDQpzZXJ2ZXIgPC0gZnVuY3Rpb24oaW5wdXQsIG91dHB1dCkgew0KICBvdXRwdXQkbmFtZSA8LSByZW5kZXJUZXh0KHsNCiAgICAgIGlucHV0JG5hbWUNCiAgICB9KQ0KfQ0KDQpzaGlueUFwcCh1aSwgc2VydmVyKQ0KDQpgYGANCg0KDQoNCiMjIFNoaW55ZGFzaGJvYXJkIExheW91dA0KDQpUaGVyZSBhcmUgdHdvIG1haW4gdHlwZXMgb2YgbGF5b3V0cyB0aGF0IHNoaW55IHVzZXMuIFRoZSBmaXJzdCBpcyBhICJyb3ciIGJhc2VkIGxheW91dCwgd2hpbGUgdGhlIG90aGVyIGlzIGEgImNvbHVtbiIgYmFzZWQgbGF5b3V0LiBZb3UgY2FuIHVzZSBlaXRoZXIgb25lLCBvciBldmVuIGEgbWl4IG9mIHRoZSB0d28uDQpUaGUgd2F5IHRvIGNyZWF0ZSB0aGUgbGF5b3V0cyBpcyBieSB1c2luZyB0aGUgImZsdWlkUm93KCkiIGZ1bmN0aW9uLiANCg0KVGhlc2UgZnVuY3Rpb25zIGFyZSB1c2VkIGluc2lkZSBvZiB0aGUgZGFzaGJvYXJkQm9keSgpIHNlY3Rpb24uIEVhY2ggdGltZSAiZmx1aWRSb3coKSIgaXMgY2FsbGVkLCBhIG5ldyByb3cgaXMgY3JlYXRlZC4gDQoNCiMjIyBSb3cgbGF5b3V0DQpgYGB7cn0NCmJvZHkgPC0gZGFzaGJvYXJkQm9keSgNCiAgZmx1aWRSb3coDQojIFJvdyAxDQogIGJveCgNCiAgd2lkdGggPSAxMiwgIyAxMiB3aWR0aCBzcGFucyB0aGUgZW50aXJlIHdpZHRoIG9mIHRoZSBzY3JlZW4NCiAgdGl0bGUgPSAiUmVndWxhciBCb3gsIFJvdyAxIiwNCiAgIlRleHQgaW5zaWRlIG9mIGJveCINCiAgKSksDQogIGZsdWlkUm93KA0KIyBSb3cgMg0KICBib3goDQogIHdpZHRoID0gMTIsDQogIHRpdGxlID0gIlJlZ3VsYXIgQm94LCBSb3cgMiIsDQogICJUZXh0IGluc2lkZSBvZiBib3ggMiINCiAgKQ0KKQ0KKQ0KDQp1aSA8LSBkYXNoYm9hcmRQYWdlKGhlYWRlciA9IGRhc2hib2FyZEhlYWRlcigpLA0KICAgICAgICAgICAgICAgICAgICBzaWRlYmFyID0gZGFzaGJvYXJkU2lkZWJhcigpLA0KICAgICAgICAgICAgICAgICAgICBib2R5ID0gYm9keQ0KICAgICAgICAgICAgICAgICAgICApDQoNCnVpDQpgYGANCg0KIyMjIENvbHVtbiBMYXlvdXQNClRoZSBuZXh0IGxheW91dCBvcHRpb24gaXMgdG8gYnVpbGQgY29sdW1ucy4gVGhlIGRpZmZlcmVuY2UgaW4gdXNpbmcgY29sdW1ucyBpbnN0ZWFkIG9mIHJvd3MgaXMgdG8gdXNlIHRoZSAiY29sdW1uKCkiIGZ1bmN0aW9uIGluc2lkZSBvZiAiZmx1aWRSb3coKSIuIA0KDQpZb3UgY2FuIHNldCB0aGUgd2lkdGggb2YgdGhlIGNvbHVtbiwgYnV0IHdoZW4geW91IGluc2VydCBhIGJveCBvciBjaGFydCwgdGhlIHdpZHRoIG5lZWRzIHRvIGJlICJOVUxMIi4gDQoNCmBgYHtyfQ0KYm9keSA8LSBkYXNoYm9hcmRCb2R5KA0KICAgIGZsdWlkUm93KA0KICAgICMgICBDb2x1bW4gMQ0KICAgICAgY29sdW1uKHdpZHRoID0gNiwNCiAgICAgIGluZm9Cb3goDQogICAgICAgIHdpZHRoID0gTlVMTCwgIyBUaGUgd2lkdGggbXVzdCBiZSAiTlVMTCIgd2hlbiB1c2luZyBhIGNvbHVtbiBsYXlvdXQNCiAgICAgICAgdGl0bGUgPSAiUmVndWxhciBCb3gsIENvbHVtbiAxIiwNCiAgICAgICAgIlRleHQgaW5zaWRlIG9mIGJveCINCiAgICAgICAgICkgICAgICANCiAgICksDQogIGNvbHVtbih3aWR0aCA9IDYsDQogICAgIyAgIENvbHVtbiAyDQogICAgICBpbmZvQm94KA0KICAgICAgICB3aWR0aCA9IE5VTEwsICMgVGhlIHdpZHRoIG11c3QgYmUgIk5VTEwiIHdoZW4gdXNpbmcgYSBjb2x1bW4gbGF5b3V0DQogICAgICAgIHRpdGxlID0gIlJlZ3VsYXIgQm94LCBDb2x1bW4gMiIsDQogICAgICAgICJUZXh0IGluc2lkZSBvZiBib3giDQogICAgICAgICApICAgICAgDQogICApDQogICkNCikNCnVpIDwtIGRhc2hib2FyZFBhZ2UoaGVhZGVyID0gZGFzaGJvYXJkSGVhZGVyKCksDQogICAgICAgICAgICAgICAgICAgIHNpZGViYXIgPSBkYXNoYm9hcmRTaWRlYmFyKCksDQogICAgICAgICAgICAgICAgICAgIGJvZHkgPSBib2R5DQogICAgICAgICAgICAgICAgICAgICkNCg0Kc2hpbnlBcHAodWksIHNlcnZlcikNCmBgYA0KDQoNCiMjIyBNaXggb2YgQ29sdW1uIGFuZCBSb3cgTGF5b3V0DQpOb3RlOg0KVG8gY3JlYXRlIGEgbmV3IHJvdywgY2FsbCBhbm90aGVyIGZsdWlkUm93KCkgZnVuY3Rpb24uIA0KYGBge3J9DQpib2R5IDwtIGRhc2hib2FyZEJvZHkoDQogICAgZmx1aWRSb3coDQogICAgIyAgIFJvdyAxDQogICAgICBib3goDQogICAgICAgIHdpZHRoID0gMTIsICMgVGhlIHdpZHRoIG11c3QgYmUgIk5VTEwiIHdoZW4gdXNpbmcgYSBjb2x1bW4gbGF5b3V0DQogICAgICAgIHRpdGxlID0gIlJlZ3VsYXIgQm94LCBSb3cgMSIsDQogICAgICAgICJUZXh0IGluc2lkZSBvZiBib3giDQogICAgICAgICApLA0KICAgICksDQogIGZsdWlkUm93KA0KICAgIGNvbHVtbih3aWR0aCA9IDYsDQogICAgIyAgIENvbHVtbiAyDQogICAgICBpbmZvQm94KA0KICAgICAgICB3aWR0aCA9IE5VTEwsICMgVGhlIHdpZHRoIG11c3QgYmUgIk5VTEwiIHdoZW4gdXNpbmcgYSBjb2x1bW4gbGF5b3V0DQogICAgICAgIHRpdGxlID0gIlJlZ3VsYXIgQm94LCBSb3cgMiwgQ29sdW1uIDEiLA0KICAgICAgICBzdWJ0aXRsZSA9ICJUZXh0IGluc2lkZSBvZiBib3giDQogICAgICAgICApDQogICAgKSwNCiAgICBjb2x1bW4od2lkdGggPSA2LA0KICAgICAgaW5mb0JveCgNCiAgICAgICAgd2lkdGggPSBOVUxMLA0KICAgICAgICB0aXRsZSA9ICJSZWd1bGFyIEJveCwgUm93IDIsIENvbHVtbiAyIiwNCiAgICAgICAgc3VidGl0bGUgPSAiVGV4dCBpbnNpZGUgb2YgYm94Ig0KICAgICAgKQ0KICAgICkNCiAgKQ0KKQ0KDQp1aSA8LSBkYXNoYm9hcmRQYWdlKGhlYWRlciA9IGRhc2hib2FyZEhlYWRlcigpLA0KICAgICAgICAgICAgICAgICAgICBzaWRlYmFyID0gZGFzaGJvYXJkU2lkZWJhcigpLA0KICAgICAgICAgICAgICAgICAgICBib2R5ID0gYm9keQ0KICAgICAgICAgICAgICAgICAgICApDQoNCnNoaW55QXBwKHVpLCBzZXJ2ZXIpDQpgYGANCg0KDQoNCg0KDQo=